import { ClassID, ID } from "./ID";
import * as Debug from './Debug';
import { HashSet } from "./Common";
import { AObject } from "./AObject";
import { ActionReportType, RoutineResult, type ActionHandler } from "./Definitions";
import { AZone } from "./AZone";
import { N } from "./NarrativeBase";
import { U } from "./Util";
import { RoutineSpecification, RoutineUtility } from "./ARoutine";

export class ClassRegistry {
    static #data = new HashSet<ClassID>;

    static register(id: ClassID) {
        this.#data.add(id);
        return id;
    }

    static get(name: string) {
        for (let id of this.#data)
            if (id.matches(name))
                return id;
        return null;
    }

    static getInternal(name: string) {
        let g = this.get(name);
        Debug.assert(g != null);
        return g;
    }
}

export class AttributeRegistry {
    static #data = new HashSet<ID>;

    static register(id: ID) {
        this.#data.add(id);
        return id;
    }

    static get(name: string) {
        for (let id of this.#data)
            if (id.matches(name))
                return id;
        return null;
    }

    static getInternal(name: string) {
        let g = this.get(name);
        Debug.assert(g != null);
        return g;
    }
}

function registerClass(name: string, det: string, alias: Iterable<string> = [], parent?: string) {
    return ClassRegistry.register(new ClassID(name, det, new Set<string>(alias), parent == undefined ? null : () => ClassRegistry.getInternal(parent)));
}

function registerAttribute(name: string, alias: Iterable<string> = []) {
    return AttributeRegistry.register(new ID(name, new Set<string>(alias)));
}

export const AClasses = {
    Key:            registerClass('钥匙', '把'),
    Door:           registerClass('门', '扇'),
    Paper:          registerClass('纸', '张'),
    Button:         registerClass('按钮', '个'),

    WritingStick:   registerClass('笔', '支'),
    Pencil:         registerClass('铅笔', '支', [], '笔'),
    Chalk:          registerClass('粉笔', '根', [], '笔'),

    Ball:           registerClass('球', '个'),
    PingpongBall:   registerClass('乒乓球', '个', [], '球'),

    Fruit:          registerClass('水果', '个'),
    Apple:          registerClass('苹果', '个', [], '水果'),
    Banana:         registerClass('香蕉', '根', [], '水果')
}

export const AAttributes = {
    Metal: registerAttribute('金属', ['金属制']),
    Glass: registerAttribute('玻璃', ['玻璃制']),
    Paper: registerAttribute('纸质', ['纸做', '纸制', '纸']),
    Wooden: registerAttribute('木质', ['木制', '木头']),

    White: registerAttribute('白色', ['白']),
    Blue: registerAttribute('蓝色', ['蓝']),
    Green: registerAttribute('绿色', ['绿']),
    Red: registerAttribute('红色', ['红']),
    Yellow: registerAttribute('黄色', ['黄']),
}

export const Make = {
    button(name: string, alias: string[], f: ActionHandler<AObject>, attrs: ID[] = []) {
        return new AObject(name, alias, {
            customRoutines: {
                Press: new RoutineSpecification<[self: AObject]>()
                    .fatigueAndTime([0.0002, 1.5])
                    .report((r, self) => U.action(r.agent, N.SP(r.agent, N.V('按下').perfective(), N.O(self))))
                    .main((r, self) => f(self, r.agent, r))
                    .ok(),
                Pull: new RoutineSpecification<[self: AObject]>()
                    .report((r) => U.metaText(r.agent, '这是非常困难的。'))
                    .effect(() => RoutineResult.CustomError())
                    .ok()
            }
        }, attrs, AClasses.Button);
    },
    /** Creates a simple door, by default openable and with a `shortDescription` showing
         * its `openState`. */
    door(dest: AZone | (() => AZone), dist: number, 
        name: string, alias: string[], args: Partial<AObject>, attrs: ID[] = [])
    {
        return new AObject(name, alias, {
            zoneTo: dest instanceof AZone ? () => [dest, dist] : () => [dest(), dist],
            isEnterable: true,
            isOpenable: true,
            shortDescription: (self, _) => 
                N.SP(self, `${self.openState ? '敞开' : '关'}着`),
            ...args,
        }, attrs, AClasses.Door);
    },
    lockable(obj: AObject, directLock: boolean, 
        directUnlock: boolean, key: AObject | null = null) 
    {
        obj.afterAction.Examine = function* (_, ag) {
            ag.character!.reportAction(
                { type: ActionReportType.LockabilityInfo, state: directLock });
            ag.character!.reportAction(
                { type: ActionReportType.UnLockabilityInfo, state: directUnlock });
            return RoutineResult.Continue();
        };
        obj.onLock = function* (_, ag, keyGiven) {
            if (directLock || key == null)
                return RoutineResult.Continue();
            if (keyGiven == null)
                return RoutineResult.KeyNeeded({object: obj, state: true});
            if (keyGiven != key)
                return RoutineResult.IncorrectKey({object: obj});
            return RoutineResult.Continue();
        };
        obj.onUnlock = function* (_, ag, keyGiven) {
            if (directUnlock || key == null)
                return RoutineResult.Continue();
            if (keyGiven == null)
                return RoutineResult.KeyNeeded({object: obj, state: false});
            if (keyGiven != key)
                return RoutineResult.IncorrectKey({object: obj});
            return RoutineResult.Continue();
        };
    }
}